/*
* Laboration 1 Datakommunikation Grupp 2
* File: webserver.cpp
* Version: 1.0
* Last modified on 2014-03-18 14:19
* Contributors : Nicklas Wallman (s131448), Olof Ivarsson(s132046), Oscar Ernstsson(s132952), Oscar Pehrson(s131469), Martin Jakobsson(s133082)
*
* This is a simple webserver.
*/


#include <stdio.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <process.h>
#include "telnet.h"
#include "config.h"
#include "http.h"
#include "logfile.h"

/* Quick network config. */
#define DEBUG_MODE 1
#define LISTEN_BACKLOG       10
#define SOCKET_TIMEOUT       10

#define INIT_SIZE            20

typedef struct
{
	SOCKET cSock;
	char *ip;

} clientT, *pClientT;

/* Prototype declarations 

*/

/*
 * Function: printContributors
 * Usage: printContributors();
 * --------------------------
 * This function prints the contributors
 */

void printContributors(void);

/*
 * Function: ClientWorker
 * Usage: _beginthread(ClientWorker, 0, client);
 * --------------------------
 * ClientWorker is a function mainly used for multithreading.
 */

void ClientWorker(LPVOID lParam);

/*
 * Function: TelnetWorker
 * Usage: _beginthread(TelnetWorker, 0, NULL);
 * --------------------------
 * Starts a new Telent server (should be used as a new thread):
 */

void TelnetWorker(LPVOID lParam);

/*
 * Function: initWSA
 * Usage: initWSA();
 * --------------------------
 * Initializes Windows Socket API (2.2).
 */

void initWSA(void);

/*
 * Global variable for checking the current status of the web server. 
 * From other files, by declaring using the extern keyword, it can be both read and modified.
 */

BOOL webSrvRunning = false;
/*
 * Global variable for handling logging issues related to threads.
 * It can be seen as a "lock" for the data to allow synchronization between threads.
 */

HANDLE logMutex;

/*
 * Config data.
 * Once the function loadConfig() has been called, this variable can be used to get the web server's config.
 */

extern configT srvConfig;

/* Prototype definitions */


void printContributors(void)
{
	printf("Webserver by:\n");
	printf("Nicklas Wallman (s131448)\nOlof Ivarsson (s132046)\nOscar Ernstsson (s132952)\nOscar Pehrson (s131469)\nMartin Jakobsson (s133082)\n\n");
}


void ClientWorker( LPVOID lParam )
{
	char *buf, *header;
	pClientT c = (clientT*)lParam;
	httpRequestT httpRequest;
	SOCKET clientSock;

	int returnCode;
	int bytesSent;
	int dwWaitResult;

	printf("\nClient %s with socket %d connected\n",c->ip,c->cSock);


	if( (parseHTTP(c->cSock,&httpRequest)) == 1 )
	{
		printf("\nCommand: %s\n",httpRequest.command);
		printf("Filename: %s\n",httpRequest.fileName);
		printf("Protocol: %s\n",httpRequest.protocol);
		if(httpRequest.conditionalDate != NULL)
			printf("ConditionalDate: %s\n",httpRequest.conditionalDate);

		bytesSent = sendPageToClient(c->cSock,httpRequest,&returnCode);

		printf("Bytes Sent: %d\n",bytesSent);

		dwWaitResult = WaitForSingleObject(logMutex, INFINITE);
		switch (dwWaitResult)
		{
		case WAIT_OBJECT_0:
			__try
			{
				saveLogfile(c->ip,httpRequest.requestDate, httpRequest.command, httpRequest.fileName, httpRequest.protocol, returnCode, bytesSent);
			}
			__finally
			{
				if (!ReleaseMutex(logMutex))
				{
					printf("Could not release mutex.");
				}
			}
			break;
		case WAIT_ABANDONED:
			return;
			break;
		default: break;
		}
	}

	// Stäng sockets

	printf("\nClient %s with socket %d disconnected\n",c->ip,c->cSock);
	closesocket(c->cSock);
	free(c);
	_endthread();
}


void TelnetWorker( LPVOID lParam )
{
	runTelnet(srvConfig.telnetPort);
	_endthread();
}

void initWSA(void)
{
	WORD wVersionReq;
	WSADATA wsaData;
	int err;

	wVersionReq = MAKEWORD(2,2);
	err = WSAStartup(wVersionReq, &wsaData);
	if (err != 0) 
	{
		fprintf(stderr, "Could not initialize Windows Socket API.");
		exit(EXIT_FAILURE);
	}
}

int main()
{
	int optval = 1;
	struct sockaddr_in srvaddr;
	SOCKET srvSock;

	printContributors();

	/* Load config and initialize Windows Socket API */

	loadConfig();
	initWSA();

	/* Create thread for telnet server */

	_beginthread(TelnetWorker, 0, NULL);

	/* Create mutex for logging */

	logMutex = CreateMutex(NULL, FALSE, NULL);
	if (logMutex == NULL)
	{
		printf("Could not create mutex. Error: %s", GetLastError());
		return 1;
	}

	/* Create server socket for web server */ 

	if ( (srvSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET )
	{
		fprintf(stderr, "Error calling socket function");
		exit(EXIT_FAILURE);
	}

	if (setsockopt(srvSock, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval)) == SOCKET_ERROR)
	{
		fprintf(stderr, "Error calling socket function");
		exit(EXIT_FAILURE);
	}

	// Initialize memory
	memset(&srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_family      = AF_INET;
	srvaddr.sin_addr.s_addr = INADDR_ANY;
	srvaddr.sin_port        = htons(srvConfig.httpPort);

	if ( bind(srvSock, (sockaddr*)&srvaddr, sizeof(srvaddr)) == SOCKET_ERROR )
	{
		fprintf(stderr, "Error binding socket.");
		exit(EXIT_FAILURE);
	}

	if (listen(srvSock, LISTEN_BACKLOG) == SOCKET_ERROR)
	{
		fprintf(stderr, "Error binding socket.");
		exit(EXIT_FAILURE);
	}

	webSrvRunning = true;

	/*  Accept incoming connections to the Web Server and start a thread for each one of them */

	SOCKET clientS;
	sockaddr_in clientInfo;
	int clientInfoLen;
	clientInfoLen = sizeof (clientInfo);
	

	for (;;)
	{
		if (DEBUG_MODE)
		{
			printf("Web server running on port %d\n", srvConfig.httpPort);
		}
		printf("Waiting for connection..\n");

		if ( (clientS = accept(srvSock, (struct sockaddr*)&clientInfo, &clientInfoLen)) == INVALID_SOCKET )
		{
			fprintf(stderr, "Could not accept client socket. Error: %d\n", WSAGetLastError());
			closesocket(clientS);
			WSACleanup();
		} 
		else 
		{
			if (webSrvRunning)
			{
				clientT *client;

				client = (clientT*) malloc(sizeof(clientT));
				client->cSock = clientS;

				client->ip = inet_ntoa(clientInfo.sin_addr);

				_beginthread(ClientWorker, 0, client);
			}
		}
	}

	closesocket(srvSock);
	WSACleanup();
	return 0;
}